package com.sunlands.zlcx.usercenter.service;

import com.sunlands.zlcx.usercenter.domain.ApiRequestStatisticsDO;
import com.sunlands.zlcx.usercenter.exception.ApiStatisticsException;
import com.sunlands.zlcx.usercenter.exception.SendExceptionUtil;
import com.sunlands.zlcx.usercenter.repository.ApiRequestStatisticsRepository;
import com.sunlands.zlcx.usercenter.util.DateUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.transaction.Transactional;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;


/**
 * @author anjl
 * 统计接口调用次数和平均消耗时长
 */
@Service
@Slf4j
public class ApiRequestStatisticsServiceImpl {

    public final static List<RequestRecord> requestRecordStore = new ArrayList<>();
    @Autowired
    private ApiRequestStatisticsRepository apiRequestStatisticsRepository;
    @Autowired
    private PathMatcher pathMatcher;
    @Autowired
    private WebApplicationContext applicationContext;


    @Transactional(rollbackOn = Exception.class)
    public void refresh(RequestRecord record) {
        String today = DateUtil.getStringDateShort();
        ApiRequestStatisticsDO statisticsDO = apiRequestStatisticsRepository.findByDayAndMethodAndUrl(today, record.getMethod(), record.getUrl());
        if (Objects.isNull(statisticsDO)) {
            statisticsDO = ApiRequestStatisticsDO.builder()
                    .method(record.getMethod())
                    .url(record.getUrl())
                    .day(today)
                    .count(0L)
                    .millisecond(0L)
                    .errorCount(0L)
                    .build();
        }

        long count = record.getCount().get();
        long errorCount = record.getErrorCount().get();
        long millisecond = record.getMillisecond().get();

        int i = 0;
        boolean countResult = false;
        while (i < 10 && !countResult) {
            countResult = record.getCount().compareAndSet(record.getCount().get(), record.getCount().get() - count);
            i++;
        }

        int j = 0;
        boolean millisecondResult = false;
        while (j < 10 && !millisecondResult) {
            millisecondResult = record.getMillisecond().compareAndSet(record.getMillisecond().get(), record.getMillisecond().get() - millisecond);
            j++;
        }

        int k = 0;
        boolean errorCountResult = false;
        while (j < 10 && !errorCountResult) {
            errorCountResult = record.getErrorCount().compareAndSet(record.getErrorCount().get(), record.getErrorCount().get() - errorCount);
            k++;
        }

        if (i > 5 || j > 5) {
            log.warn("更新请求次数和耗时次数 i = {} j = {} k = {}", i, j, k);
            sendExceptionUtil.sendException("更新请求次数和耗时次数 i = " + i + " j = " + j + "k = " + k, new ApiStatisticsException());
        }

        statisticsDO.setCount(Optional.ofNullable(statisticsDO.getCount()).orElse(0L) + count);
        statisticsDO.setErrorCount(Optional.ofNullable(statisticsDO.getErrorCount()).orElse(0L) + errorCount);
        statisticsDO.setMillisecond(Optional.ofNullable(statisticsDO.getMillisecond()).orElse(0L) + millisecond);

        apiRequestStatisticsRepository.save(statisticsDO);
    }

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class RequestRecord {
        String method;
        String url;
        AtomicLong count;
        AtomicLong errorCount;
        AtomicLong millisecond;
    }

    public void recordError(HttpServletRequest request) {
        String method = request.getMethod();
        String url = request.getRequestURI();
        if (requestRecordStore.isEmpty()) {
            synchronized (this) {
                if (requestRecordStore.isEmpty()) {
                    init();
                }
            }
        }
        requestRecordStore.stream()
                .filter(record -> record.method.equals(method) && pathMatcher.match(record.url, url))
                .forEach(record -> {
                    record.errorCount.incrementAndGet();
                });
    }

    public void record(String method, String url, Long millisecond) {
        if (requestRecordStore.isEmpty()) {
            synchronized (this) {
                if (requestRecordStore.isEmpty()) {
                    init();
                }
            }
        }
        requestRecordStore.stream()
                .filter(record -> record.method.equals(method) && pathMatcher.match(record.url, url))
                .forEach(record -> {
                    int i = 0;
                    boolean result = false;
                    while (i < 10 && !result) {
                        result = record.millisecond.compareAndSet(record.millisecond.get(), record.millisecond.get() + millisecond);
                        i++;
                    }
                    if (i > 5) {
                        log.info("记录请求时长重试次数 i = {}", i);

                    }
                    record.count.incrementAndGet();
                });
    }

    private void init() {
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        //获取url与类和方法的对应信息
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        for (RequestMappingInfo info : map.keySet()) {
            //获取url的Set集合，一个方法可能对应多个url
            String url = info.getPatternsCondition().getPatterns().stream().findFirst().orElse(null);
            RequestMethod method = info.getMethodsCondition().getMethods().stream().findFirst().orElse(null);
            if (Objects.isNull(url) || Objects.isNull(method)) {
                return;
            }
            RequestRecord record = RequestRecord.builder()
                    .method(method.name())
                    .url(url)
                    .count(new AtomicLong())
                    .errorCount(new AtomicLong())
                    .millisecond(new AtomicLong())
                    .build();
            requestRecordStore.add(record);
        }
    }

    @Autowired
    private SendExceptionUtil sendExceptionUtil;
}
